package butines.core;

import java.awt.DisplayMode;
import java.awt.Graphics2D;
import java.awt.GraphicsDevice;
import java.awt.GraphicsEnvironment;
import java.awt.Toolkit;
import java.awt.image.BufferStrategy;

import javax.sound.sampled.AudioFileFormat.Type;
import javax.swing.JFrame;

import butines.core.gui.GUIManager;
import butines.core.gui.GUIPainel;



public abstract class GameCore implements Game, InputCodes {

	public static final float SECOND = 1000000000f; // 1 Seconds

	public static long getTime() {
		return System.nanoTime();
	}
	
	private static GameCore instance;
	
	public static GameCore getInstance() {
		return instance;
	}
	
	private JFrame window;
	
	private boolean running;
	private float frameRate;
	private int fps;
	private int ups;

	private GameCursor gameCursor;

	private VideoManager videoManager;
	private InputManager inputManager;
	private AudioManager audioManager;
	private GUIManager   guiManager;
	
	
	public GameCore() {
		GraphicsEnvironment environment = GraphicsEnvironment.getLocalGraphicsEnvironment();
		GraphicsDevice graphicsDevice = environment.getDefaultScreenDevice();

		window = new JFrame(graphicsDevice.getDefaultConfiguration());
		window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        window.setResizable(false);
        window.setUndecorated(true);
//        window.setAutoRequestFocus(true);
        window.setFocusable(true);
        window.requestFocus();
		
		videoManager = new VideoManager(graphicsDevice);
		videoManager.init(window);

		audioManager = new AudioManager();
		audioManager.init(44100, Type.WAVE, 32);
		
		inputManager = new InputManager(window);
		
		guiManager = new GUIManager();
		
		setFrameRate(60);
		setWindow(800, 500);
		
		instance = this;
	}
	
	public JFrame getWindow() {
	    return window;
	}
	
	public GameCursor getGameCursor() {
		return gameCursor;
	}

	public void setGameCursor(GameCursor gameCursor) {
		this.gameCursor = gameCursor;
	}

	public void setFullScreen(DisplayMode displayMode) {
	    videoManager.setFullScreen(window, displayMode);
	}
	
   public void setFullScreen(int width, int height) {
        videoManager.setFullScreen(window, width, height);
    }
	
	public void setWindow(int width, int height) {
        videoManager.setWindow(window, width, height);
	}
	
	public boolean isFullScreen() {
		return videoManager.isFullScreen();
	}
	
	public int getWidth() {
		return videoManager.getWidth();
	}

	public int getHeight() {
	    return videoManager.getHeight();
	}

	public int getFps() {
		return fps;
	}

	public int getUps() {
	    return ups;
	}
	
	public float getFrameRate() {
		return frameRate;
	}

	public void setFrameRate(float frameRate) {
		this.frameRate = frameRate;
	}
	
	public void startup() {
		try {
			init();
			gameLoop();
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
		    destroy();
		    lazilyExit();
		}
		audioManager.shutdown();
		guiManager.shutdown();
		inputManager.shutdown();
        videoManager.shutdown(window);
        System.exit(0);
	}

	public void shutdown() {
		running = false;
	}
	
	private void tick(float delta) {
	    update(delta);
        guiManager.update(inputManager, delta);
        if (gameCursor != null) {
            gameCursor.update(delta);
        }
	}
	
    private void render() {
        BufferStrategy bufferStrategy = window.getBufferStrategy();
        if (bufferStrategy == null) {
            return;
        }
        
        Graphics2D g2d = (Graphics2D) bufferStrategy.getDrawGraphics();
        
        render(g2d);
        guiManager.render(g2d);
        if (gameCursor != null) {
            gameCursor.render(g2d, getMouseX(), getMouseY());
        }

        g2d.dispose();
        
        if (bufferStrategy.contentsLost() == false) {
            bufferStrategy.show();
            Toolkit.getDefaultToolkit().sync();
        }
        
    }
 
	private void gameLoop() {

		long lastTime = getTime();
		long lastRecord = lastTime;
		int  frames = 0;
		int  ticks  = 0;

		float delta = 1f / frameRate; // Seconds per Ticks
		float unprocessed = 0;
		
		running = true;
		
		while (running) {

			boolean shouldRender = false;
			while ( unprocessed >= delta) {
			    unprocessed -= delta;
			    ticks++;
			    tick(delta);
			    shouldRender = true;
			}
			
			if (shouldRender) {
			    frames++;
			    render();
			}
			
            long now = getTime();

            if (now - lastRecord >= SECOND) {
                lastRecord = now;
                fps = frames;
                ups = ticks;
                frames = 0;
                ticks  = 0;
            }

            unprocessed += (now - lastTime) / SECOND;

            lastTime = now;
			
			Thread.yield();
		}

	}

	private void lazilyExit() {
		Thread thread = new Thread("lazyExit") {
			public void run() {
				// first, wait for the VM exit on its own.
				try {
					Thread.sleep(2000);
				} catch (InterruptedException ex) {
					// ignored
				}
				// system is still running, so force an exit
				System.exit(0);
			}
		};
		thread.setDaemon(true);
		thread.start();
	}
	
	public void addPainel(GUIPainel painel) {
	    guiManager.add(painel);
	}
	
	public void removePainel(GUIPainel painel) {
        guiManager.remove(painel);
    }
	
    public void setSystemCursorVisible(boolean visible) {
        videoManager.setCursorVisible(window, visible);
    }
    
    public boolean isSystemCursorVisible() {
        return videoManager.isCursorVisible();
    }
    
	// Delegate method for InputHandler //
	public void defineActionKey(GameAction action, int keyCode) {
		inputManager.defineActionKey(action, keyCode);
	}
	
	public void defineActionMouse(GameAction action, int mouseCode) {
		inputManager.defineActionMouse(action, mouseCode);
	}

	public void removeAction(GameAction action) {
		inputManager.removeAction(action);
	}
	
	public void setRelativeMouseMode(boolean mode) {
		inputManager.setRelativeMouseMode(mode);
	}

	public boolean isRelativeMouseMode() {
		return inputManager.isRelativeMouseMode();
	}

	public int getMouseX() {
		return inputManager.getMouseX();
	}
	
	public int getMouseY() {
		return inputManager.getMouseY();
	}
	
	public Mouse getMouse() {
	    return inputManager;
	}
	
	// Delegate method for SystemSound
	public void loadSound(String path) {
		audioManager.loadSound(path);
	}

	public void removeSound(String path) {
	    audioManager.removeSound(path);
	}

	public void playSound(String path, boolean blocking) {
	    audioManager.playSound(path, blocking);
	}
	
}
